package com.itlong.cloud.controller.wechat;

import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.core.type.TypeReference;
import com.itlong.cloud.POJO.PO.WeChatOfficialAuthInfoPO;
import com.itlong.cloud.PlatformConstants;
import com.itlong.cloud.annotation.UnAuthAccessRequired;
import com.itlong.cloud.constants.RedisConstant;
import com.itlong.cloud.service.RedisService;
import com.itlong.cloud.user.property.IPropertyWeChatServiceClient;
import com.itlong.cloud.utils.encrypt.WeChatUtil;
import com.itlong.cloud.utils.network.http.HttpProtocolUtil;
import com.itlong.cloud.utils.node.JsonUtil;
import com.itlong.cloud.utils.node.XMLParser;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.w3c.dom.Document;

import javax.servlet.http.HttpServletRequest;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.ByteArrayOutputStream;
import java.util.HashMap;
import java.util.Map;

/**
 * <desc>
 *      第三方平台(本平台)接收微信推送控制器
 * </desc>
 *
 * @createDate 2019/09/06
 */
@RestController
@RequestMapping("/receive/weChat")
@RefreshScope
public class ReceiveWeChatPushController {

    @Value("${" + PlatformConstants.THIRD_PARTY_APPID + ":}")
    public String THIRD_PARTY_APPID;
    @Value("${" + PlatformConstants.THIRD_PARTY_TOKEN + ":}")
    public String THIRD_PARTY_TOKEN;
    @Value("${" + PlatformConstants.THIRD_PARTY_ENCODING_AES_KEY + ":}")
    public String THIRD_PARTY_ENCODING_AES_KEY;

    private static final Logger LOG = LoggerFactory.getLogger(ReceiveWeChatPushController.class);

    @Autowired
    private RedisService redisService;

    @Autowired
    private IPropertyWeChatServiceClient iPropertyWeChatServiceClient;

    /**
     * <desc>
     *      接收微信推送第三方平台的ticket，字段说明：
     *      字段名称	                        字段描述
     *      AppId	                        第三方平台appid
     *      CreateTime	                    时间戳
     *      InfoType	                    component_verify_ticket
     *      ComponentVerifyTicket	        Ticket内容
     *
     *      component_verify_ticket的有效时间较component_access_token更长，建议保存最近可用的component_verify_ticket，
     *      在component_access_token过期之前使用该ticket进行更新，避免出现因为ticket接收失败而无法更新component_access_token的情况。
     *      第三方平台component_access_token是第三方平台的下文中接口的调用凭据，也叫做令牌（component_access_token）。
     *      每个令牌是存在有效期（2小时）的，且令牌的调用不是无限制的，请第三方平台做好令牌的管理，在令牌快过期时（比如1小时50分）再进行刷新。
     * </desc>
     *
     * @return 字符串success
     * @author wangzhi
     * @createDate 2019/09/06
     */
    @UnAuthAccessRequired
    @RequestMapping("receiveComponentVerifyTicket")
    public String receiveComponentVerifyTicket(){
//        LOG.info("微信推送第三方平台的ticket请求接入");
        try {
            Map<Object, Object> oldTicketMap = (Map<Object, Object>) redisService.getObject(RedisConstant.WECHAT_COMPONENT_VERIFY_TICKET, Map.class);
//            LOG.info("原ticket：" + oldTicketMap);
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
            String requestUrl = request.getRequestURL().toString();
            String queryString = request.getQueryString();
            // 签名串
            String msgSignature = request.getParameter("msg_signature");
            // 时间戳
            String timeStamp = request.getParameter("timestamp");
            // 随机串
            String nonce = request.getParameter("nonce");
            // 密文，对应POST请求的数据，xml类型的post数据，接收方式不同
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            DocumentBuilder db = dbf.newDocumentBuilder();
            Document doc = db.parse(request.getInputStream());
//            LOG.info("密文流数据初步获取：" + doc);
            String postData = this.getXmlString(doc);
//            String postData = request.getParameter("encrypt_msg");
//            LOG.info("请求路径：" + requestUrl + "?" + queryString + "，接收ticket密文：" + postData + "，时间戳：" + timeStamp + "，签名串：" + msgSignature + "，随机串：" + nonce);
            // 进行解密
            String result = WeChatUtil.decryptMsg(msgSignature, timeStamp, nonce, postData, THIRD_PARTY_APPID, THIRD_PARTY_TOKEN, THIRD_PARTY_ENCODING_AES_KEY);
            LOG.info("解密后明文：" + result);
            Map<Object, Object> resultMap = XMLParser.xmlString2Map(result);
            LOG.info("xml转换后"+resultMap.toString());
            redisService.set(RedisConstant.WECHAT_COMPONENT_VERIFY_TICKET, RedisConstant.WECHAT_COMPONENT_VERIFY_TICKET_TIME, resultMap);
            return "success";
        } catch (Exception e) {
            LOG.info("接收ticket发生异常：" + e);
            return "success";
        }
    }

    /**
     * <desc>
     *      接收微信回调的授权码和过期时间
     * </desc>
     *
     * @return 字符串success
     * @author wangzhi
     * @createDate 2019/09/09
     */
    @UnAuthAccessRequired
//    @RequestMapping("receiveAuthCode/{userId}")
    public String receiveAuthCode(@PathVariable(name = "userId") String userId){
        try {
            LOG.info("微信公众号授权回调进入，userId=" + userId);
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
            // 拿到授权码
            String authCode = request.getParameter("auth_code");
            // 用一个PO对象初步保存一些信息，然后发到service进行处理
            WeChatOfficialAuthInfoPO weChatOfficialAuthInfoPO = new WeChatOfficialAuthInfoPO();
            weChatOfficialAuthInfoPO.setPropertyUserId(userId);
            // 通过授权码调用公众相关API
            // 从缓存中拿到component_verify_ticket
            Map<Object, Object> ticketMap = (Map<Object, Object>) redisService.getObject(RedisConstant.WECHAT_COMPONENT_VERIFY_TICKET, Map.class);
            // 获取第三方平台component_access_token
            String componentAccessToken = redisService.get(RedisConstant.WECHAT_COMPONENT_ACCESS_TOKEN);
            if (StringUtils.isBlank(componentAccessToken)) {
                Map<String, Object> getTokenMap = new HashMap<>();
                getTokenMap.put("component_appid", ticketMap.get("AppId"));
                getTokenMap.put("component_appsecret", "1a227418e22b5e604241867b820ef8f7");
                getTokenMap.put("component_verify_ticket", ticketMap.get("ComponentVerifyTicket"));
                String tokenResult = HttpProtocolUtil.httpPost("https://api.weixin.qq.com/cgi-bin/component/api_component_token", JSON.toJSONString(getTokenMap));
                Map<String, Object> tokenResultMap = JsonUtil.decode(tokenResult, new TypeReference<Map<String, Object>>() {});
                Object tokenObject = tokenResultMap.get("component_access_token");
                if (tokenObject != null) {
                    componentAccessToken = tokenObject.toString();
                } else {
                    // 后续要有一个获取token失败的备用处理方案
                    return "success";
                }
                redisService.set(RedisConstant.WECHAT_COMPONENT_ACCESS_TOKEN, RedisConstant.WECHAT_COMPONENT_ACCESS_TOKEN_TIME, componentAccessToken);
            }
            // 使用授权码换取公众号或小程序的接口调用凭据和授权信息
            Map<String, Object> getApiAuthMap = new HashMap<>();
            getApiAuthMap.put("component_appid", ticketMap.get("AppId"));
            getApiAuthMap.put("authorization_code", authCode);
            String apiAuthResult = HttpProtocolUtil.httpPost("https://api.weixin.qq.com/cgi-bin/component/api_query_auth?component_access_token=" + componentAccessToken,
                    JSON.toJSONString(getApiAuthMap));
            weChatOfficialAuthInfoPO.setRawInformation("接口调用凭据和授权信息：" + apiAuthResult);
            Map<String, Object> apiAuthResultMap = JsonUtil.decode(apiAuthResult, new TypeReference<Map<String, Object>>() {});
            // 获取授权方appid
            String authorizerAppid = ((Map<String, Object>) apiAuthResultMap.get("authorization_info")).get("authorizer_appid") + "";
            weChatOfficialAuthInfoPO.setAppId(authorizerAppid);
            // 获取授权方的帐号基本信息
            Map<String, Object> getAuthInfoMap = new HashMap<>();
            getAuthInfoMap.put("component_appid", ticketMap.get("AppId"));
            getAuthInfoMap.put("authorizer_appid", authorizerAppid);
            String authInfoResult = HttpProtocolUtil.httpPost("https://api.weixin.qq.com/cgi-bin/component/api_get_authorizer_info?component_access_token=" + componentAccessToken,
                    JSON.toJSONString(getAuthInfoMap));
            weChatOfficialAuthInfoPO.setRawInformation(weChatOfficialAuthInfoPO.getRawInformation() + "&授权方的帐号基本信息：" + authInfoResult);
            Map<String, Object> authInfoResultMap = JsonUtil.decode(authInfoResult, new TypeReference<Map<String, Object>>() {});
            weChatOfficialAuthInfoPO.setPrincipalName(((Map<String, Object>) authInfoResultMap.get("authorizer_info")).get("principal_name") + "");
            weChatOfficialAuthInfoPO.setNickName(((Map<String, Object>) authInfoResultMap.get("authorizer_info")).get("nick_name") + "");
            iPropertyWeChatServiceClient.saveWeChatAuthInfoCallBack(weChatOfficialAuthInfoPO);
            return "success";
        } catch (Exception e) {
            LOG.info("接收授权码发生异常：" + e);
            return "success";
        }
    }

    /**
     * <desc>
     *      XML转字符串，原样取出
     * </desc>
     *
     * @return XML转字符串
     * @author wangzhi
     * @createDate 2019/09/10
     */
    private String getXmlString(Document doc){
        TransformerFactory tf = TransformerFactory.newInstance();
        try {
            Transformer t = tf.newTransformer();
            t.setOutputProperty(OutputKeys.ENCODING,"UTF-8");
            t.setOutputProperty(OutputKeys.METHOD, "html");
            t.setOutputProperty(OutputKeys.VERSION, "4.0");
            t.setOutputProperty(OutputKeys.INDENT, "no");
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            t.transform(new DOMSource(doc), new StreamResult(bos));
            return bos.toString();
        } catch (TransformerConfigurationException e) {
            e.printStackTrace();
        } catch (TransformerException e) {
            e.printStackTrace();
        }
        return "";
    }

}
